package com.link2room.rodimus.facade.impl;

import com.link2room.common.constant.DefaultValue;
import com.link2room.common.enums.BaseSysExceptionEnum;
import com.link2room.common.exception.SysException;
import com.link2room.common.jpa.result.QueryResult;
import com.link2room.common.service.ICommonService;
import com.link2room.common.util.L2RAssert;
import com.link2room.rodimus.dto.param.base.SignDistributeAwardParamDto;
import com.link2room.rodimus.dto.param.base.SignInAwardBaseDto;
import com.link2room.rodimus.dto.param.base.SignInBaseDto;
import com.link2room.rodimus.dto.param.base.SignInDetailDto;
import com.link2room.rodimus.entity.SignInAwardDrawDetailEntity;
import com.link2room.rodimus.entity.SignInDetailEntity;
import com.link2room.rodimus.facade.ISignInAwardHandler;
import com.link2room.rodimus.listener.event.SignInDistributeAwardEvent;
import com.link2room.rodimus.service.ISignInService;
import com.link2room.rodimus.util.CalendarUtils;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

import static com.link2room.enums.exception.RodimusSysExceptionEnum.SIGN_IN_DISTRIBUTE_AWARD;

/**
 * Created by LN on 2018/1/8
 */
@Component("signInAwardHandler")
public class SignInAwardHandlerImpl implements ISignInAwardHandler {

    @Resource
    private ISignInService signInService;

    @Resource
    private ICommonService commonService;

    @Autowired
    private RedisLockRegistry distributeAwardLock;

    @Autowired
    private ApplicationEventPublisher publisher;

    @Override
    public void distributeAward(SignDistributeAwardParamDto paramDto) throws Exception {
        SignInDetailDto signInDetailDto = paramDto.getSignInDetailDto();
        L2RAssert.isTrue(null == signInDetailDto, new SysException(SIGN_IN_DISTRIBUTE_AWARD, "[signInDetailDto] is required."));
        String lhotelGroupCode = signInDetailDto.getLhotelGroupCode();
        Date signInDateMonth = CalendarUtils.getStartTimeOfMonth(signInDetailDto.getSignInDate());
        SignInBaseDto underwaySignInBaseDto = signInService.getUnderwaySignBaseWithAwardInfo(lhotelGroupCode);
        L2RAssert.isTrue(null == underwaySignInBaseDto, new SysException(SIGN_IN_DISTRIBUTE_AWARD, "[underwaySignInBaseDto] is required"));
        List<SignInAwardBaseDto> signInAwardBases = underwaySignInBaseDto.getSignInAwardBases();
        L2RAssert.isTrue(CollectionUtils.isEmpty(signInAwardBases), new SysException(SIGN_IN_DISTRIBUTE_AWARD, "当前签到活动没有奖品映射.[ruleBaseCode] is " + underwaySignInBaseDto.getCode()));
        Lock lock = obtainDistributedLock(underwaySignInBaseDto.getCode(), signInDetailDto.getMemberNo());
        List<SignInAwardDrawDetailEntity> signInAwardDrawDetailEntities = null;
        try {
            signInAwardDrawDetailEntities = signInAwardBases.stream()
                    .filter(awardBaseDto -> couldDistributed(underwaySignInBaseDto.getCode(), awardBaseDto, signInDetailDto))
                    .map(awardBaseDto -> {
                        SignInAwardDrawDetailEntity signInAwardDrawDetailEntity = new SignInAwardDrawDetailEntity();
                        signInAwardDrawDetailEntity.setActivityCode(underwaySignInBaseDto.getCode());
                        signInAwardDrawDetailEntity.setMemberNo(signInDetailDto.getMemberNo());
                        signInAwardDrawDetailEntity.setHasDrawn(DefaultValue.F);
                        signInAwardDrawDetailEntity.setLhotelGroupCode(signInDetailDto.getLhotelGroupCode());
                        signInAwardDrawDetailEntity.setMonth(signInDateMonth);
                        signInAwardDrawDetailEntity.setAwardCode(awardBaseDto.getCode());
                        return signInAwardDrawDetailEntity;
                    })
                    .collect(Collectors.toList());
            commonService.saveList(signInAwardDrawDetailEntities);


        } finally {
            lock.unlock();
        }


        //每日奖励发放
        publisher.publishEvent(new SignInDistributeAwardEvent(
                signInAwardBases.stream()
                        .filter(entity->signInService.isEveryAward(entity.getDayNum(),entity.getCouldLoopDraw()))
                        .map(awardBaseDto -> {
                            SignInAwardDrawDetailEntity signInAwardDrawDetailEntity = new SignInAwardDrawDetailEntity();
                            signInAwardDrawDetailEntity.setActivityCode(underwaySignInBaseDto.getCode());
                            signInAwardDrawDetailEntity.setMemberNo(signInDetailDto.getMemberNo());
                            signInAwardDrawDetailEntity.setHasDrawn(DefaultValue.F);
                            signInAwardDrawDetailEntity.setLhotelGroupCode(signInDetailDto.getLhotelGroupCode());
                            signInAwardDrawDetailEntity.setMonth(signInDateMonth);
                            signInAwardDrawDetailEntity.setAwardCode(awardBaseDto.getCode());
                            return signInAwardDrawDetailEntity;
                        })
                        .collect(Collectors.toList())));
    }

    private Lock obtainDistributedLock(String activityCode, String memberNo) {
        String lockKey = activityCode + "|" + memberNo;
        Lock lock = distributeAwardLock.obtain(lockKey);
        try {
            if (lock.tryLock(30, TimeUnit.SECONDS)) {
                return lock;
            } else {
                throw new SysException(BaseSysExceptionEnum.BASE_FUCATION_OPTION, "server is busy");
            }
        } catch (InterruptedException e) {
            throw new SysException(BaseSysExceptionEnum.BASE_FUCATION_OPTION, e);
        }
    }

    private boolean couldDistributed(String activityCode, SignInAwardBaseDto awardBaseDto, SignInDetailDto signInDetailDto) {

        boolean couldLoopDraw = DefaultValue.T.equals(awardBaseDto.getCouldLoopDraw());


        Date month = CalendarUtils.getStartTimeOfMonth(signInDetailDto.getSignInDate());
        Date signInDate = CalendarUtils.getDateWithOutTime(signInDetailDto.getSignInDate());
        Integer dayNum = awardBaseDto.getDayNum() == 0 ? 1 : awardBaseDto.getDayNum();

        QueryResult<SignInDetailEntity> signInDetails = null;
        try {
            signInDetails = signInService.signInDetail(signInDate, signInDetailDto.getLhotelGroupCode(), signInDetailDto.getMemberNo());
        } catch (Exception e) {
            throw new SysException(BaseSysExceptionEnum.BASE_FUCATION_OPTION, e);
        }
        int shouldAwardQuantity = 0;
        if (CollectionUtils.isNotEmpty(signInDetails.getResultlist())) {
            List<Date> signInDates = signInDetails.getResultlist()
                    .stream()
                    .map(SignInDetailEntity::getSignInDate)
                    .distinct()
                    .collect(Collectors.toList());

            shouldAwardQuantity = CalendarUtils.countSeries(signInDates, dayNum);
        }


        int count = signInAwardDrawDetailCount(signInDetailDto.getLhotelGroupCode(), awardBaseDto.getCode(), signInDetailDto.getMemberNo(), month, activityCode);

        if (couldLoopDraw) {
            return shouldAwardQuantity > count;
        } else {
            return count == 0 && shouldAwardQuantity > 0;

        }
    }

    private int signInAwardDrawDetailCount(String lhotelGroupCode, String awardCode, String memberNo, Date month, String activityCode) {
        SignInAwardDrawDetailEntity findDrawDetail = new SignInAwardDrawDetailEntity();
        findDrawDetail.setLhotelGroupCode(lhotelGroupCode);
        findDrawDetail.setAwardCode(awardCode);
        findDrawDetail.setMemberNo(memberNo);
        findDrawDetail.setMonth(month);
        findDrawDetail.setActivityCode(activityCode);
        try {
            return (int) commonService.getCountAllEQ(SignInAwardDrawDetailEntity.class, findDrawDetail, null);

        } catch (Exception e) {
            throw new SysException(BaseSysExceptionEnum.BASE_FUCATION_OPTION, e);
        }
    }
}
